ECB CBC WTF
Here you can encrypt in CBC but only decrypt in ECB. That shouldn't be a weakness because they're different modes... right?
source.py
from Crypto.Cipher import AES
KEY = ?
FLAG = ?
@chal.route('/ecbcbcwtf/decrypt/<ciphertext>/')
def decrypt(ciphertext):
ciphertext = bytes.fromhex(ciphertext)
cipher = AES.new(KEY, AES.MODE_ECB)
try:
decrypted = cipher.decrypt(ciphertext)
except ValueError as e:
return {"error": str(e)}
return {"plaintext": decrypted.hex()}
@chal.route('/ecbcbcwtf/encrypt_flag/')
def encrypt_flag():
iv = os.urandom(16)
cipher = AES.new(KEY, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(FLAG.encode())
ciphertext = iv.hex() + encrypted.hex()
return {"ciphertext": ciphertext}
Solution:
The problem here is that the decrypt
function is using ECB mode, but the encrypt_flag
function is using CBC mode. This means that the first block of the ciphertext is the IV, and the rest of the ciphertext is the encrypted flag. The IV is random, so we can't just guess it. However, we can use the fact that ECB mode is deterministic to our advantage.
we know and and after decryption with so will get and for new block and repeat the process for each block of ciphertext and get the flag. Code for the the same is
solve.py
import requests
# for decryption
url_dc = "https://aes.cryptohack.org/ecbcbcwtf/decrypt/"
url_en = "https://aes.cryptohack.org/ecbcbcwtf/encrypt_flag/"
from Crypto.Util.number import long_to_bytes
def decrypt(ciphertext):
r=requests.get(url_dc+ciphertext)
return r.json()['plaintext']
def main():
# get ciphertext
r = requests.get(url_en)
c = r.json()['ciphertext']
iv=int(c[:32],16)
flag=b""
c=c[32:] #remove iv
for i in range(0,len(c),32):
dec=decrypt(c[i:i+32])
block = int(dec,16)
flag+=long_to_bytes(iv^block)
iv=int(c[i:i+32],16)
print(flag)
main()
After running the code we get the flag crypto{3cb_5uck5_4v01d_17_!!!!!}